8487 2020-04-05 2024-11-10

本篇将着重分析Spring是如何处理一个请求的。

一、DispatcherServlet

在Spring完全启动后,服务器就可以正常响应web请求了。我们看下请求入口。

1、FrameworkServlet

// org.springframework.web.servlet.FrameworkServlet
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
	// 省略其他
}

// 本质是一个 Servlet
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
	// 省略其他
}

// 三个方法均来自 DispatcherServlet 的父类 FrameworkServlet
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    processRequest(request, response);
}

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
	// 记录请求处理耗时
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
	// 一般为null
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    // 整合request中的Locale
    LocaleContext localeContext = buildLocaleContext(request);
	// 默认为null
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    // 简单的将request和response放进类 ServletRequestAttributes中
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
        // 真正的逻辑处理doService留给了子类DispatcherServlet
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

方法中已经开始了对请求的处理,虽然把细节转移到了doService方法中实现,但是我们不难看出来处理请求前后所做的不少准备与结尾工作,如下

  • 为了保证当前线程的LocaleContext以及RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性。
  • 根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程。
  • 委托给doService方法进一步处理。
  • 请求处理结束后恢复线程到原始状态。
  • 请求处理结束后发布事件通知。

1、对Locale的处理

// 子类DispatchServlet重写了父类FrameServlet的buildLocaleContext方法
// 注意,虽然上面3个方法是调用父类的,但实际请求发送给的是DispatchServlet,而并非FrameServlet
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
    // 这就是上节我们提到过的 LocaleResolver,默认为AcceptHeaderLocaleResolver
    LocaleResolver lr = this.localeResolver;
    if (lr instanceof LocaleContextResolver) {
        return ((LocaleContextResolver) lr).resolveLocaleContext(request);
    }
    // 走到这里
    else {
        return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
    }
}

// 进到这里来了
@Override
public Locale resolveLocale(HttpServletRequest request) {
    // 默认为null,可以自定义
    Locale defaultLocale = getDefaultLocale();
    // 如果默认Locale不为null且浏览器端没有指定语言环境
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    }
    // 进一步进tomcat走了一万个方法
    // 最后得出requestLocale = zh_CN
    Locale requestLocale = request.getLocale();
    // 默认为空
    List<Locale> supportedLocales = getSupportedLocales();
    if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
        return requestLocale;
    }
    Locale supportedLocale = findSupportedLocale(request, supportedLocales);
    if (supportedLocale != null) {
        return supportedLocale;
    }
    return (defaultLocale != null ? defaultLocale : requestLocale);
}

LocaleResolver默认实现为

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

2、WebAsyncManager

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

// 简单的注册属性,暂时还没发现有什么用途
public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) {
    WebAsyncManager asyncManager = null;
    Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE);
    if (asyncManagerAttr instanceof WebAsyncManager) {
        asyncManager = (WebAsyncManager) asyncManagerAttr;
    }
    if (asyncManager == null) {
        asyncManager = new WebAsyncManager();
        servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);
    }
    return asyncManager;
}

// 暂时还没发现有什么用
/**
 * CallableProcessingInterceptor implementation that initializes and resets
 * FrameworkServlet's context holders, i.e. LocaleContextHolder and RequestContextHolder.
 */
private class RequestBindingInterceptor implements CallableProcessingInterceptor {

    @Override
    public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
            // 这一行
            initContextHolders(request, buildLocaleContext(request),
                    buildRequestAttributes(request, response, null));
        }
    }
    @Override
    public <T> void postProcess(NativeWebRequest webRequest, Callable<T> task, Object concurrentResult) {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            resetContextHolders(request, null, null);
        }
    }
}

3、initContextHolders*

// 为RequestContextHolder注入HttpServletRequest,绑定到ThreadLocal
private void initContextHolders(HttpServletRequest request,
        @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {

    if (localeContext != null) {
        LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
    }
    if (requestAttributes != null) {
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
    }
}

这个地方初看可能会觉得很奇怪,可仔细一跟源码就会豁然开朗,如下

// RequestContextHolder逻辑一模一样,这里就不列了
public final class LocaleContextHolder {

	private static final ThreadLocal<LocaleContext> localeContextHolder =
			new NamedThreadLocal<>("LocaleContext");

	private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
			new NamedInheritableThreadLocal<>("LocaleContext");
	// 省略其他
}

public static void setLocaleContext(@Nullable LocaleContext localeContext, boolean inheritable) {
    if (localeContext == null) {
        resetLocaleContext();
    }
    else {
        if (inheritable) {
            inheritableLocaleContextHolder.set(localeContext);
            localeContextHolder.remove();
        }
        else {
            localeContextHolder.set(localeContext);
            inheritableLocaleContextHolder.remove();
        }
    }
}

于是我们可以在单个请求中的任何地方调用如下代码以后去request和response,形如

@RequestMapping("/testStr")
@ResponseBody
public String testStr() {
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
    return request + "  " + response;
}

4、MVC默认组件

来自jar包 org.springframework:spring-webmvc:5.2.5.REALEASE

除此之外,Spring Boot为了简化开发&用户体验,会替我们自动注入绝大部分组件,以满足基本可用。

注意,即使 Spring Boot 不显式注入组件,Spring MVC 自带的默认组件其实满足基本需求的。但为了灵活性和便于管理,Spring Boot替我们做了绝大部分配置工作(显式注入配置 > 默认配置,所以其实默认组件配置是没有生效的)。

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

二、doService

接下来重点看下doService方法,如下

// 来自 FrameworkServlet 子类 DispatcherServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // debug一下请求
    logRequest(request);
    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    // 这一段是对jsp中include指令的支持,具体细节这里暂时跳过去
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    // set进了DispatchServlet的WebApplicationContext
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    // 对FlashMap的处理,解决redirect中传递参数失效的问题
    // forward不存在参数丢失的问题
    if (this.flashMapManager != null) {
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    try {
        // 这里才是真正的逻辑调用
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}

doService方法中并没有过多的逻辑,除了注入request一些框架组件外(方便后面的处理),还有就是对 FlashMap 的处理了。如下是一个简单示例

// 目前只发现这一种实现方式,经过多番实践,这个东西并没有什么卵用,建议忘掉 FlashMap
@RequestMapping("/redirect2")
public String redirect2(String username, String password, RedirectAttributes redirectAttributes) {
    // 这里传入的参数会出现在重定向后的url
    redirectAttributes.addAttribute("username", username);
    redirectAttributes.addAttribute("password", password);
    return "redirect: /redirectTarget";
}

@RequestMapping("/redirectTarget")
@ResponseBody
public String redirectTarget(String username, String password, HttpServletRequest request) {
    return "username=" + username + ",password=" + password;
}

1、doDispatch

doService将真正逻辑处理委托给了doDispatch方法,如下

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // 如果是MultiparContent类型的request类型则转换request为MultipartHttpServletRequest类型的request
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            // 根据request信息寻找对应的Handler
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                // 如果没有找到对应的handler则通过response反馈错误信息
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            // 根据当前handler寻找对应的HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // 拦截器的preHandler方法的调用
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // 真正的激活handler并返回视图
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
			// 视图名称转换应用于需要添加前缀后缀的情况
            applyDefaultViewName(processedRequest, mv);
            // 应用所有拦截器的postHander方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        // 处理请求返回的处理结果
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        // 触发 AfterCompletion
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        // 触发 AfterCompletion
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

doDispatch方法中展示了Spring请求处理所涉及的主要逻辑,而我们之前设置在request中的各种辅助属性也都有被派上了用场。下面回顾以下逻辑处理的全过程。

2、checkMultipart

对于请求的处理,Spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换request为MultipartHttpServletRequest类型的request。

processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 如果指定了MultipartResolver
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
                logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        }
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                    "skipping re-resolution for undisturbed error rendering");
        }
        else {
            try {
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                    logger.debug("Multipart resolution failed for error dispatch", ex);
                    // Keep processing error dispatch with regular request handle below
                }
                else {
                    throw ex;
                }
            }
        }
    }
    // If not returned before: return original request.
    return request;
}

// 来源于类 StandardServletMultipartResolver
// 一般是根据multipart/form-data
@Override
public boolean isMultipart(HttpServletRequest request) {
    // Same check as in Commons FileUpload...
    if (!"post".equalsIgnoreCase(request.getMethod())) {
        return false;
    }
    String contentType = request.getContentType();
    return StringUtils.startsWithIgnoreCase(contentType, "multipart/");
}

@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
    return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}

// 后面就是对http协议中的文件转换了
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
        throws MultipartException {

    super(request);
    if (!lazyParsing) {
        parseRequest(request);
    }
}

3、getHandler

// Determine handler for the current request.
// 根据request信息寻找对应的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
    // 如果没有找到对应的handler则通过response反馈错误信息
    noHandlerFound(processedRequest, response);
    return;
}

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 注意这里的handlerMappings的个数为5个
    // 原因是加载配置文件时,自定义标签mvc已经配置好了需要的bean
    // 为 RequestMappingHandlerMapping、BeanNameUrlHandlerMapping
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            // 遍历5个hander,找到则返回,找不到则404
            HandlerExecutionChain handler = mapping.getHandler(request);
            // 如果找对对应的handler则直接返回,默认优先查 RequestMappingHandlerMapping,第一优先级
            if (handler != null) {
                // 找到即返回
                return handler;
            }
        }
    }
    return null;
}

在之前的内容我们提过,在系统启动时Spring会将所有的映射类型的bean注册到this.handlerMappings变量中,所以此方法的目的就是遍历所有的HanderMapping,并调用getHandler方法进行封装处理。其中,5个 HandlerMapping 分别为

  • RequestMappingHandlerMapping:支持 @RequestMapping、@GetMapping 等注解,最常用。
  • **BeanNameUrlHandlerMapping:**根据beanName寻找路由,可忽略。
  • **RouterFunctionMapping:**支持函数式路由编程 RouterFunction,这个在Web Flux里面用的比较多。
  • SimpleUrlHandlerMapping:简单url路径匹配bean,可忽略。
  • WelcomePageHandlerMapping:匹配默认首页 /。

以上配置来自Spring Boot默认配置类 WebMvcAutoConfiguration,后两个组件为Spring Boot独有。

1、RequestMapping

RequestMappingHandlerMapping 为例查看其 getHandler 方法。

// getHandler方法还是由父类 AbstractHandlerMapping 提供
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 抽象方法,由子类重写
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }
	// 执行拦截器链
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    if (logger.isTraceEnabled()) {
        logger.trace("Mapped to " + handler);
    }
    else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
        logger.debug("Mapped to " + executionChain.getHandler());
    }
	// 跨域问题
    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        config = (config != null ? config.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}

// 类RequestMappingHandlerMapping重写了父类方法
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    try {
        // 又调回了父类
        return super.getHandlerInternal(request);
    }
    finally {
        ProducesRequestCondition.clearMediaTypesAttribute(request);
    }
}

2、getHandlerInternal

/**
 * Look up a handler method for the given request.
 */
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 截取用于匹配的url有效路径,如请求是${app}/test_url/a,那么这里就是test_url/a
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    request.setAttribute(LOOKUP_PATH, lookupPath);
    // 获取内部维护的读写锁ReentrantReadWriteLock的读锁
    // 内部缓存了Controller处理方法与url对映射关系
    this.mappingRegistry.acquireReadLock();
    try {
        // 根据路径寻找Handler
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        // 释放读锁
        this.mappingRegistry.releaseReadLock();
    }
}

// 来自类 AbstractHandlerMethodMapping,找到匹配的 HandlerMethod
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // 先查找缓存,自定义标签mvc会事先解析所有请求并存进这里,所以这里一般不为空
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }

    if (!matches.isEmpty()) {
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        matches.sort(comparator);
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                String uri = request.getRequestURI();
                throw new IllegalStateException(
                        "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
            }
        }
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

3、添加拦截器

在寻找到 HandlerMethod 后,接着将配置中的对应的拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
    // HandlerInterceptor Spring拦截器
    // 这里默认有两个 ConversionServiceExposingInterceptor、ResourceUrlProviderExposingInterceptor
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        else {
            // 走到这里,记录拦截链
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

4、处理跨域

// 可以看到是在这一行就进行CORS判断,两个条件:
// 1. 是否配置了CORS,如果不配的话,默认是返回false的
// 2. 或者当前请求是OPTIONS请求,且头里包含ORIGIN和ACCESS_CONTROL_REQUEST_METHOD                
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
    CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
    CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    config = (config != null ? config.combine(handlerConfig) : handlerConfig);
    // 添加 CorsInterceptor 拦截器
    executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}

private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
    @Nullable
    private final CorsConfiguration config;

    public CorsInterceptor(@Nullable CorsConfiguration config) {
        this.config = config;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        // Consistent with CorsFilter, ignore ASYNC dispatches
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        if (asyncManager.hasConcurrentResult()) {
            return true;
        }

        return corsProcessor.processRequest(this.config, request, response);
    }

    @Override
    @Nullable
    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
        return this.config;
    }
}

4、noHandlerFound

每个请求都应该对应着一个Hander,因为每个请求都会在后台有相应的逻辑对应,而逻辑的实现就是在Handler中,所以一旦遇到没有找到相应Handler的情况(包含默认),就只能通过response向用户返回错误信息。

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (pageNotFoundLogger.isWarnEnabled()) {
        pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
    }
    // 默认为false
    if (this.throwExceptionIfNoHandlerFound) {
        throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
                new ServletServerHttpRequest(request).getHeaders());
    }
    else {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

5、getHandlerAdapter

我们先理一下概念:

  • HandlerMapping:定于url与HandlerMethod的匹配关系,如 RequestMappingHandlerMapping。

  • HandlerMethod:url所对应的具体bean处理方法。

  • HandlerExecutionChain:Spring拦截器链,用于执行拦截器。

  • HandlerAdapter:用于执行HandlerMethod。

这里我们重点看一下 RequestMappingHandlerAdapter

注意,这里只是找到,还未进行调用。

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());


// 这一步只是寻找,判断的规则也很简单,如下
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    // 这里默认会有四个,分别是 RequestMappingHandlerAdapter、HandlerFunctionAdapter、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter,优先级依次降低
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

// 来源于类 AbstractHandlerMethodAdapter,一般用于处理@RequestMapping注解,与@Controller配合使用
@Override
public final boolean supports(Object handler) {
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

// 来自于类 RequestMappingHandlerAdapter
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
    return true;
}

更为复杂的调用逻辑是在后面实现的(如拦截器链条逻辑的应用)。

6、LastModified

在研究Spring对缓存处理的功能支持前,我们先了解一个概念:Last-Modified缓存机制。

  • 在客户端第一次输入URL时,服务端会返回内容和状态吗200,表示请求成功,同时会添加一个“Last-Modified”的响应头,表示此文件在服务器上的最后更新时间,例如“Last-Modified:Wed, 14 Mar 2020 20:26:22 GMT”。
  • 客户端第二次请求此URL时,客户端会向服务器发送请求头"If-Modified-Since",询问服务器该时间之后当前请求内容是否有被修改过,如果服务器端的内容没有变化,则自动返回HTTP 304状态码(只要响应头,内容为空,这样就节省了网络带宽)。

Spring提供了对 Last-Modified 机制的支持,只需要实现LastModified接口,如下所示

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
        return;
    }
}

@Component("/last")
public class LastModifiedCacheController extends AbstractController implements LastModified {
    private long lastModifiedTime;
    private static final DateTimeFormatter DTF_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String str = parseLongTime(lastModifiedTime);
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("<h2>当前时间:" + str + "<h2>");
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request) {
        if (lastModifiedTime == 0) {
            lastModifiedTime = System.currentTimeMillis();
        }
        // 这一行决定了是否总是返回最新值,注释掉则每次都返回第一次访问的时间,不注释则每次返回最新时间
        // lastModifiedTime = System.currentTimeMillis();
        return lastModifiedTime;
    }

    public static String parseLongTime(long longTime) {
        Instant instant = Instant.ofEpochMilli(longTime);
        LocalDateTime nowTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return DTF_DATE_TIME.format(nowTime);
    }
}

Spring判断是否过期,通过判断请求的“If-Modified-Since”是否大于等于当前的getLastModified方法的时间戳,如果是,则认为没有修改。否则认为修改了返回最新值。

三、处理请求

上面的都是一些准备工作,这一块才算是正式执行自定义业务相关代码了。

1、applyPreHandle

Servlet API定义的servlet过滤器可以在servlet处理每个Web请求的前后分别对它进行前置处理和后置处理。

此外有些时候,你可能只想处理由某些Spring MVC组件处理的Web请求,并在这些处理程序返回的模型属性被传递到视图之前,对他们进行一些操作。

这时就到了HandlerInterceptor ,如下例子。

// 自定义拦截器
public class TestInterceptor implements HandlerInterceptor {

    private Logger log = LogManager.getLogger(this.getClass());

    /**
     * 如果请求放行(调用Controller之前),则返回true,否则返回false
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.debug("放行请求:" + request.getRequestURI());
        sleep(100);
        return true;
    }

    /**
     * 请求已被处理完(调用Controller之后,返回逻辑视图之前),调用此方法
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.debug("执行请求完,返回" + response.getStatus());
        sleep(100);
    }

    /**
     * 在返回逻辑试图之后(通常用来释放资源),调用此方法
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.debug("关闭连接资源");
        sleep(100);
    }
}

1、applyPreHandle

// 通过前文我们已经看到了 spring web添加了两个默认的拦截器
// ConversionServiceExposingInterceptor,数据转换与绑定
// ResourceUrlProviderExposingInterceptor,支持访问静态资源
// 另外,如果配置允许跨域,则会多一个拦截器 CorsInterceptor
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // Spring会帮我们内置两个HandlerInterceptor,用于实现Web的特定拦截,只有返回true,则会放行请求
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 即使不通过,仍会执行 triggerAfterCompletion
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}

2、applyPostHandle

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
        throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // 还是前面两个,需要主要的是,后处理的执行顺序是倒过来的,这个与Spring Cloud Gateway很像
        // 即前面是123经过拦截器,后面是321流出拦截器
        for (int i = interceptors.length - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

注意这里请求流入与流出的顺序。

2、ha.handle**

handler方法算是Spring MVC中的重中之重,主要分为两个部分,在找到目标方法后,如何调用方法?又怎么处理方法的返回值?这里为了具体化,我简单列几个比较关注的点吧,如下

  • 调用方法中参数是如何确定、如何精确绑定的?包括个数、类型
  • @RequestParam、@ResponseBody、@PathVariable等注解如何生效?
  • 如何处理返回的非String类型对象?
  • Param参数自动注入的是如何进行的?

1、ModelAndView

先看下Spring MVC中MV的定义。这里的M - model可能有点狭义,但V - view还是比较好理解的。

// Actually invoke the handler.
// 如果是视图页面,则mv不为null,反之则为null
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

public class ModelAndView {

	/** View instance or view name String. */
	@Nullable
	private Object view;

	/** Model Map. */
	@Nullable
	private ModelMap model;

	/** Optional HTTP status for the response. */
	@Nullable
	private HttpStatus status;

	/** Indicates whether or not this instance has been cleared with a call to {@link #clear()}. */
	private boolean cleared = false;
    
 	// 省略其他get/set方法
}

public interface View {

	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";

	String PATH_VARIABLES = View.class.getName() + ".pathVariables";

	String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";

    // 确定返回类型,json or html/text or 二进制流
	@Nullable
	default String getContentType() {
		return null;
	}

    // 渲染视图界面
	void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
			throws Exception;
}

2、handleInternal

/**
 * This implementation expects the handler to be an {@link HandlerMethod}.
 */
// 这里还是以ReqeustMappingHandlerAdapter为例
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

    return handleInternal(request, response, (HandlerMethod) handler);
}


@Override
protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);

    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No HttpSession available -> no mutex necessary
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    else {
        // No synchronization on session demanded at all...
        // 我们主要看下这里,这一步之后已经确定入参参数以及需要返回值类型
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            prepareResponse(response);
        }
    }

    return mav;
}

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        // 这里的参数解析支持有26种,后文简单列下
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        // 返回值解析有15种
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
		// 这个mvcContainer很重要
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
		// 这里主要是一些对于异步的支持
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            LogFormatUtils.traceDebug(logger, traceOn -> {
                String formatted = LogFormatUtils.formatValue(result, !traceOn);
                return "Resume with async result [" + formatted + "]";
            });
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }
		// 实际调用处,我们主要看下这个方法
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
	// 这里已经取得了返回值
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);

    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // 这一步主要是解析返回值类型
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
	// 这一步过后,Spring MVC已经可以得到了特定位置参数的值,即已经完成了参数的自动注入
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    // 接下来开始利用参数调用方法,这里返回的是方法结果值
    return doInvoke(args);
}

实际的Controller方法调用并不复杂,找到对应的Bean调用即可。

关键是对于此方法的入参(例如支持Request、Map、String、表单/json、自定义类型注入等),以及返回值的处理(可以返回String、ModelAndView、还有二进制流等)。

3、入参和返回值

1、26种HandlerMethodArgumentResolver

这里列下之前提到的26种用于解析参数的类,如下(限于篇幅不做过多展开,大致作用就是给与特定参数以特定资源)。

当然,我们也完全可以自定义HandlerMethodArgumentResolver,已实现自定类型的资源注入。

0 = {RequestParamMethodArgumentResolver@6892} 
1 = {RequestParamMapMethodArgumentResolver@6893} 
2 = {PathVariableMethodArgumentResolver@6894} 
3 = {PathVariableMapMethodArgumentResolver@6895} 
4 = {MatrixVariableMethodArgumentResolver@6896} 
5 = {MatrixVariableMapMethodArgumentResolver@6897} 
6 = {ServletModelAttributeMethodProcessor@6898} 
7 = {RequestResponseBodyMethodProcessor@6899} 
8 = {RequestPartMethodArgumentResolver@6900} 
9 = {RequestHeaderMethodArgumentResolver@6901} 
10 = {RequestHeaderMapMethodArgumentResolver@6902} 
11 = {ServletCookieValueMethodArgumentResolver@6903} 
12 = {ExpressionValueMethodArgumentResolver@6904} 
13 = {SessionAttributeMethodArgumentResolver@6905} 
14 = {RequestAttributeMethodArgumentResolver@6906} 
15 = {ServletRequestMethodArgumentResolver@6907} 
16 = {ServletResponseMethodArgumentResolver@6908} 
17 = {HttpEntityMethodProcessor@6909} 
18 = {RedirectAttributesMethodArgumentResolver@6910} 
19 = {ModelMethodProcessor@6911} 
20 = {MapMethodProcessor@6912} 
21 = {ErrorsMethodArgumentResolver@6913} 
22 = {SessionStatusMethodArgumentResolver@6914} 
23 = {UriComponentsBuilderMethodArgumentResolver@6915} 
24 = {RequestParamMethodArgumentResolver@6916} 
25 = {ServletModelAttributeMethodProcessor@6917} 

大致过一下这些类的定义,会发现Spring默认提供了很多功能,但我们平时常用的就那么一点,这一部分有待研究,暂时跳过。

如下是一个实际的例子

public class RequestContextArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        // 需要注入资源的类
        return methodParameter.getParameterType().equals(RequestContext.class);
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        // 事先在拦截器进行token验证以及token存放
        RequestContext requestContext = (RequestContext) request.getAttribute("XX_TOKEN");
        // 如果为空则表示没有传token或者token已过期
        if (requestContext == null || requestContext.getUserInfo() == null) {
            throw new LoginExpireException();
        }
        return requestContext;
    }
}

2、15种HandlerMethodReturnValueHandler

这里列下之前提到的15种用于解析方法返回值的类,如下限于篇幅不做过多展开,大致作用就是根据方法返回的不同类型,决定请求的响应结果)。

0 = {ModelAndViewMethodReturnValueHandler@6501} 
1 = {ModelMethodProcessor@6502} 
2 = {ViewMethodReturnValueHandler@6503} 
3 = {ResponseBodyEmitterReturnValueHandler@6504} 
4 = {StreamingResponseBodyReturnValueHandler@6505} 
5 = {HttpEntityMethodProcessor@6506} 
6 = {HttpHeadersReturnValueHandler@6507} 
7 = {CallableMethodReturnValueHandler@6508} 
8 = {DeferredResultMethodReturnValueHandler@6509} 
9 = {AsyncTaskMethodReturnValueHandler@6510} 
10 = {ModelAttributeMethodProcessor@6511} 
11 = {RequestResponseBodyMethodProcessor@6512} 
12 = {ViewNameMethodReturnValueHandler@6513} 
13 = {MapMethodProcessor@6514} 
14 = {ModelAttributeMethodProcessor@6515} 

大概还是跟上面一样吧,Spring默认提供了很多功能,兼顾了绝大部分场景,这里不做过多展开。

3、自定义参数和返回值

1、顶层接口

通过代码,我们知道了上述过程,虽然不知道内部的实现细节。我们照葫芦画瓢,试着去自己实现一个,先上父接口

// 最顶层解析参数的接口
public interface HandlerMethodArgumentResolver {

	boolean supportsParameter(MethodParameter parameter);

	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

// 顶层接口处理返回值
public interface HandlerMethodReturnValueHandler {

	boolean supportsReturnType(MethodParameter returnType);

	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

// 顶层接口转换结果消息,一般用于 @ResponseBody 的转换
// 而对于@ResponseBody处理的类为 RequestResponseBodyMethodProcessor
// 该类同时实现了 HandlerMethodArgumentResolver 和 HandlerMethodReturnValueHandler 接口
public interface HttpMessageConverter<T> {

	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    
	List<MediaType> getSupportedMediaTypes();

	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;
}

2、关键代码

关键代码如下

@RequestMapping("/testParam")
// 如果使用@ResponseBody的话,等于是选择了RequestResponseBodyMethodProcessor来处理返回值
// 这是可能需要自定义一个HttpMessageConverter,以便让RequestResponseBodyMethodProcessor进行对象转换 
public User testParam(@CurrentUser User user) {
    System.out.println(user);
    return user;
}

public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 存在一个为 User 类型且被 @CurrentUser 注解修饰的参数
        return parameter.getParameterType().isAssignableFrom(User.class)
                && parameter.hasParameterAnnotation(CurrentUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        // 给予资源
        User user = new User();
        user.setId(1);
        user.setName("11");
        user.setAge(21);
        return user;
    }
}

public class UserMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        System.out.println("supportsReturnType" + " " + returnType + " " + returnType.getParameterType());
        // 支持返回类型为 User.class
        return returnType.getParameterType() == User.class;
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 不渲染视图,自定义处理
        mavContainer.setRequestHandled(true);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        Assert.state(response != null, "No HttpServletResponse");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        System.out.println("handleReturnValue" + returnValue + " " + returnType + " " + mavContainer + " " + webRequest);
        response.getWriter().write(returnValue.toString());
        response.flushBuffer();
    }
}

通过实现 WebMvcConfigurer 类的 addArgumentResolversaddReturnValueHandlers 方法以在Spring Boot快速注入自定义组件。

3、自定义HttpMessageConverter

需要配合 HttpMessageConverter,不然Spring Boot无法正确识别Http状态码。所以,非必要情况下,我们不需要自定返回参数解析器。

@RequestMapping("/testParam")
@ResponseBody
public User testParam(@CurrentUser User user) {
    System.out.println(user);
    return user;
}

@Component
public class UserMessageConvert implements HttpMessageConverter<User>, ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        RequestMappingHandlerAdapter adapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
        if (!adapter.getMessageConverters().contains(this)) {
            adapter.getMessageConverters().add(this);
            System.out.println("onApplicationEvent成功添加自定义转换器");
        }
    }

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return clazz == User.class;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz == User.class;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        // 不能返回null
        List<MediaType> list = new ArrayList<MediaType>(2);
        list.add(MediaType.TEXT_PLAIN);
        return list;
    }

    @Override
    public User read(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputMessage.getBody()));
        StringBuilder s = new StringBuilder();
        String temp;
        while ((temp = bufferedReader.readLine()) != null) {
            s.append(temp);
        }
        System.out.println(s);
        return null;
    }

    @Override
    public void write(User user, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        outputMessage.getBody().write(user.toString().getBytes());
    }
}

限于篇幅,点到为止。

4、applyDefaultViewName

// 视图名称转换应用于需要添加前缀后缀的情况
applyDefaultViewName(processedRequest, mv);

/**
 * Do we need view name translation?
 */
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
    // 如果返回的是视图页面,但有没有指定视图名称,则返回默认视图
    if (mv != null && !mv.hasView()) {
        // 添加前后缀,由 RequestToViewNameTranslator viewNameTranslator 提供支持
        String defaultViewName = getDefaultViewName(request);
        if (defaultViewName != null) {
            mv.setViewName(defaultViewName);
        }
    }
}

四、收尾工作

Spring在这一步对请求结果进行处理,不管最终的结果是流数据、还是视图界面、还是抛出异常,都在这里统一处理。我们先来回顾一下代码,如下

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    try {
			// 省略部分代码
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            // 这里记录一下可能出现的异常
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            // 而针对Error,则直接抛错即可
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
    	// 这里
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        // 这里调用拦截器中afterCompletion
        // 注意这里把异常传进去了
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
        @Nullable Exception exception) throws Exception {
    boolean errorView = false;
	// 如果处理请求过程中出现了异常
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            // 大部分情况下异常捕获会走到此处
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            // 针对异常做全局处理
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        // 如果返回的是一个页面,那么这一步开始渲染界面
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

1、异常处理

对于找不到的请求url,可以直接报404,但对于正常情况下的服务器处理请求错误,那么就会进到这里,可以决定是否报500还是200。

// 方法均来自类 DispatchServlet
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
        @Nullable Object handler, Exception ex) throws Exception {

    // Success and error responses may use different content types
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    // Spring Boot帮我们内置了2个 DefaultErrorAttributes、HandlerExceptionResolverComposite
    // 其中第二个位复合型,又包含3个子项:ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver
    if (this.handlerExceptionResolvers != null) {
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            exMv = resolver.resolveException(request, response, handler, ex);
            // 有一个能够找到,则说明返回结果
            if (exMv != null) {
                break;
            }
        }
    }
    // 如果exMV为null,则说明异常处理器不能处理该异常,不为null则代表以处理
    if (exMv != null) {
        // 处理为空的特例,只返回状态码
        if (exMv.isEmpty()) {
            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            return null;
        }
        // We might still need view name translation for a plain error model...
        if (!exMv.hasView()) {
            String defaultViewName = getDefaultViewName(request);
            if (defaultViewName != null) {
                exMv.setViewName(defaultViewName);
            }
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Using resolved error view: " + exMv, ex);
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Using resolved error view: " + exMv);
        }
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }
	// 无法处理,则直接抛出去
    throw ex;
}

2、渲染界面

Spring Boot中常见的html、jsp、thymeleaf、beetl界面渲染就是此步进行的。我们看下渲染界面的逻辑,如下

// 方法均来自类 DispatchServlet
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale =
            (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    // 解析loccal,国内一般为zh_CN,可参考前面对Locale的处理
    response.setLocale(locale);

    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        // We need to resolve the view name.
        // 开始根据视图名解析视图了
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        // 找不到视图对应的页面
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                    "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view [" + view + "] ");
    }
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        // 开始渲染,输出字节流
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}

@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
        Locale locale, HttpServletRequest request) throws Exception {
	// 注意这里的逻辑,如果有多个视图解析器的话,那么返回第一个视图解析器能够处理的View
    // 在有多个视图解析器的情况下,这一步可能会发送意料之外的情况,例如期待a页面被解析器1解析,而却被解析器2解析
    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            // 这一步留给了视图解析器很大的空间,我就在这一步中遇到过坑,这一步里面包含的view资源可能存在,也可能不存在,看解析器里面是否做了资源存在性的检查
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

// 这里以类 InternalResourceViewResolver 为例,大致过下
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    // 是否启用缓存,默认为true
    if (!isCache()) {
        return createView(viewName, locale);
    }
    else {
        // 所以一般走的是缓存,如果有的话
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);
        if (view == null) {
            // 第一次创建页面
            synchronized (this.viewCreationCache) {
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    // Ask the subclass to create the View object.
                    // 可能有资源存在性的检查,这里示例代码是没有的
                    view = createView(viewName, locale);
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }
                    if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                    }
                }
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace(formatKey(cacheKey) + "served from cache");
            }
        }
        return (view != UNRESOLVED_VIEW ? view : null);
    }
}

@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
        HttpServletResponse response) throws Exception {

    if (logger.isDebugEnabled()) {
        logger.debug("View " + formatViewName() +
                ", model " + (model != null ? model : Collections.emptyMap()) +
                (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
    }

    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

@Override
protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    // Expose the model object as request attributes.
    exposeModelAsRequestAttributes(model, request);

    // Expose helpers as request attributes, if any.
    exposeHelpers(request);

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(request, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(request, response)) {
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including [" + getUrl() + "]");
        }
        rd.include(request, response);
    }

    else {
        // Note: The forwarded resource is supposed to determine the content type itself.
        if (logger.isDebugEnabled()) {
            logger.debug("Forwarding to [" + getUrl() + "]");
        }
        // 这里使用Servlet中标准的RequestDispatcher来进行转发
        // Spring并不直接负责文件页面的输出,而是委托给了Servlet容器
        rd.forward(request, response);
    }
}

3、triggerAfterCompletion

// 调用拦截器中的各个afterCompletion
private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {

    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, ex);
    }
    throw ex;
}

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
        throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                // 倒序调用,且传入异常信息
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}

至此,Spring MVC总算告一段落了。后续将不定时深入分析Spring在工作常见的一些问题及其背后源码、设计思想,希望能对读者有所帮助。

总访问次数: 23次, 一般般帅 创建于 2020-04-05, 最后更新于 2024-11-10

进大厂! 欢迎关注微信公众号,第一时间掌握最新动态!